//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Expressions;

namespace OpenLS.Spreadsheet
{
    public partial class Cell
    {
        #region IOfficeXmlSerializable Members

        internal void ReadXml(ReadContext context, int previousColumnIndex, StyleRecordManager styleRecordManager)
        {
            Contract.Requires(styleRecordManager != null);
            using (ReadContext c = context.Read(xmlName))
            {

                string range = c.GetOptionalString("r");
                if (range != null)
                    parseRange(range);
                else
                    Column = Row.Worksheet.Columns[previousColumnIndex + 1];
                var cellType = c.GetOptionalString("t", "n");
                var ii = c.GetOptionalInteger("s");
                if (ii != null)
                    FormattingRecord = styleRecordManager[ii.Value];
#if DEBUG
                if (Label == "A1")
                    Debug.WriteLine(this.HorizontalAlignment);
#endif
                bool broken = false;
                while (!broken)
                {
                    switch (c.ReaderLocalName)
                    {
                        case null:
                            broken = true;
                            break;
                        case "v":
                            ReadValue(c, cellType);
                            break;
                        case "f":
                            readFormula(c);
                            break;
                        case "is":
                            ReadInlineString(c);
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                }
            }
        }

        internal static CellSerializationData ReadXmlData(ReadContext context, int previousColumnIndex,
                                                          StyleRecordManager styleRecordManager)
        {
            Contract.Requires(styleRecordManager != null);
            CellSerializationData result = new CellSerializationData();
            using (ReadContext c = context.Read(xmlName))
            {
                result.range = c.GetOptionalString("r");
                result.cellType = c.GetOptionalString("t", "n");
                result.StyleRecordIndex = c.GetOptionalInteger("s");
                bool broken = false;
                while (!broken)
                {
                    string n = c.ReaderLocalName;
                    switch (n)
                    {
                        case null:
                            broken = true;
                            break;
                        case "v":

                            result.Value = c.ReadElementString("v");
                            break;
                        case "f":
                            result.Formula = c.ReadElement<SerializationCellFormula>();
                            break;
                        case "is":
                            result.InlineString = c.ReadElementAs<RichTextParagraph>("is");
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                }
            }
            return result;
        }

        private void ReadInlineString(ReadContext c)
        {
            Value = c.ReadElementAs<RichTextParagraph>("is");
        }

        private void readFormula(ReadContext c)
        {
            var cellFormula = c.ReadElement<SerializationCellFormula>();
            //\\   if (cellFormula.aca)  
            //\\   throw new NotImplementedException();
            switch (cellFormula.Type)
            {
                case CellFormulaType.Normal:
                    if (cellFormula.ca && string.IsNullOrEmpty(cellFormula.Text))
                        break;
                    c.GetData<WorkbookResolver>().AddFormula(this, cellFormula.Text);
                        //\\ this.Workbook.TempReader.AddFormula(this, f.Text);
                    break;
                case CellFormulaType.Shared:
                    if (!string.IsNullOrEmpty(cellFormula.Text))
                    {
                        c.GetData<WorkbookResolver>().AddSharedFormula(this, cellFormula.Reference, cellFormula.Text,
                                                                       cellFormula.si);
                    }
                    else
                        c.GetData<WorkbookResolver>().AddSharedFormulaRef(this, cellFormula.si);
                    break;
                case CellFormulaType.Array:
                    if (!string.IsNullOrEmpty(cellFormula.Text))
                    {
                        c.GetData<WorkbookResolver>().AddArrayFormula(this, cellFormula.Reference, cellFormula.Text,
                                                                      cellFormula.si);
                    }
                    else
                    {
                        throw new NotImplementedException(); //\\
                    }
                    break;
                case CellFormulaType.DataTable:
                    throw new NotImplementedException();

                default:
                    throw new NotSupportedException();
            }
        }

        private void writeFormula(WriteContext context, string f)
        {
            var arrayFormulaReference = _expression as ArrayFormulaReference;
            if (arrayFormulaReference != null)
            {
                Range range = arrayFormulaReference.ArrayFormula.Range;
                if (range.TopLeftCell != this)
                    return;
                using (var c = context.Write("f"))
                {
                    c.SetString("ref", range.Label);
                    c.SetEnum("t", CellFormulaType.Array);
                    c.WriteString(f);
                }
                return;
            }
            var sharedExpressionReference = _expression as SharedExpressionReference;
            if (sharedExpressionReference == null)
            {
                context.WriteElementString("f", f);
                return;
            }
            using (WriteContext c = context.Write("f"))
            {
                var manager = c.GetData<SharedFormulaManager>();
                c.SetEnum("t", CellFormulaType.Shared);
                if (!manager.Contains(sharedExpressionReference.SharedExpression))
                {
                    c.SetString("ref", sharedExpressionReference.SharedExpression.Address.Label);
                    c.SetInteger("si", manager.Add(sharedExpressionReference.SharedExpression));
                    c.WriteString(sharedExpressionReference.GetText(new TextContext(new CellEvaluationContext(this))));
                }
                else
                {
                    c.SetInteger("si", manager.Get(sharedExpressionReference.SharedExpression));
                }
            }

        }

        private class FastParser
        {
            internal static int ParseInt32(string s)
            {
                Contract.Ensures(Contract.Result<int>() == int.Parse(s));
                int len = s.Length;
                int result = 0;
                for (int i = 0; i < len; i++)
                {
                    result *= 10;
                    result += (s[i] - '0');
                }
                return result;
            }
        }

        private void ReadValue(ReadContext c, string type)
        {
            string s = c.ReadElementString("v");
            switch (type)
            {
                case "s": // :CellType.SharedString:
                    if (Value == null)
                        setRawValue(Row.Worksheet.Workbook.SharedStrings[FastParser.ParseInt32(s)]);
                    break;
                case "n": //CellType.Number:
                    if (Value == null) // not implemented
                        setRawValue(double.Parse(s, CultureInfo.InvariantCulture));
                    //\\TextAlignment = TextAlignment.Right;
                    break;
                case "b": //CellType.Boolean:
                    Value = GetBooleanValue(s);
                    break;
                case "e": //CellType.Error:
                    Value = Error.Parse(s);
                    //\\Value = new Error(s);
                    break;
                case "str": //CellType.FormulaString:
                    Value = XmlStringHelper.Decode(s);
                    break; //\\
                default:
                    throw new NotSupportedException();
            }
        }

        /*void WriteXml(WriteContext context)
        {
            throw new NotSupportedException();
        }*/

        #endregion

        internal void WriteXml(WriteContext context, CellAddress address)
        {
            if (IsEmpty)
                return;
            using (WriteContext c = context.Write("c"))
            {
                c.SetString("r", address.Label);
                if (FormattingRecord != null && FormattingRecord.Id != 0)
                {
                    Contract.Assert(this.Workbook.StyleSheet.ContainsRecord(this.FormattingRecord));
                    c.SetInteger("s", FormattingRecord.Id);
                }
                if (Value != null)
                    c.SetEnum("t", getCellType());
                string f = FormulaWithoutEqual;
                if (f != null && Equals(address, Address))
                {
                    writeFormula(c, f);
                }
                if (Value == null)
                {
                }
                else if (Value is string)
                {
                    if (HasExtraData && EnsureExtraData.Paragraph != null)
                    {
                        int index = Workbook.SharedStrings.GetIndex(EnsureExtraData.Paragraph);
                        c.WriteElementString("v", index.ToString());
                    }
                    else
                    {
                        var s = (string) Value;
                        if (Formula == null)
                        {
                            int index = Workbook.SharedStrings.GetIndex(s);
                            c.WriteElementString("v", index.ToString());
                        }
                        else
                        {
                            c.WriteElementString("v", XmlStringHelper.Encode(s));
                        }
                    }
                }
                else if (Value is Error)
                {
                    string s = ((Error) Value).Code;
                    c.WriteElementString("v", s);
                }
                else if (Value is double || Value is float || Value is decimal)
                {
                    string s = string.Format(CultureInfo.InvariantCulture, "{0}", Value);
                    Contract.Assert(!s.Contains(","));
                    c.WriteElementString("v", s);
                }
                else if (Value is int || Value is long || Value is ulong || Value is uint || Value is byte ||
                         Value is sbyte || Value is short || Value is ushort)
                {
                    string s = string.Format(CultureInfo.InvariantCulture, "{0}", Value);
                    Contract.Assert(!s.Contains(","));
                    c.WriteElementString("v", s);
                }
                else if (Value is Complex)
                {
                    c.WriteElementString("v", ((Complex)Value).ToString(CultureInfo.InvariantCulture));
                }
                else if (Value is DateTime)
                {
                    c.WriteElementString("v", Workbook.ChangeType<double>((DateTime) Value).ToString());
                }
                else if (Value is bool)
                {
                    if ((bool) Value)
                        c.WriteElementString("v", "1");
                    else
                        c.WriteElementString("v", "0");
                }
                else
                {
                    c.WriteElementString("v", Errors.ValueError.Code);
                }
            }
        }

        internal static bool GetBooleanValue(string rawValue)
        {
            switch (rawValue)
            {
                case "1":
                    return true;
                case "0":
                    return false;
                default:
                    throw new NotSupportedException();
            }
        }


        internal class CellSerializationData
        {
            public string range;
            public string cellType;
            public int? StyleRecordIndex;
            public string Value;
            public SerializationCellFormula Formula;
            public RichTextParagraph InlineString;
           
            public int Apply(Row row, int previousColumnIndex, StyleRecordManager styleRecordManager, WorkbookResolver workbookResolver)
            {
                Contract.Ensures(Contract.Result<int>() > previousColumnIndex);
                Contract.Assert(previousColumnIndex < Worksheet.MaxColumnIndex);
                Cell cell = Cell.CreateCellWithRow(row);
                if (range != null)
                    cell.parseRange(range);
                else
                    cell.Column = cell.Row.Worksheet.Columns[previousColumnIndex + 1];
                if (this.StyleRecordIndex != null)
                {
                    cell.FormattingRecord = styleRecordManager[StyleRecordIndex.Value];
                }
                if (this.Value != null)
                        {
                            switch (this.cellType)
                            {
                                case "s": // :CellType.SharedString:
                                    {
                                        object o = cell.Row.Worksheet.Workbook.SharedStrings[
                                            FastParser.ParseInt32(Value)];
                                        if (o is RichTextParagraph)
                                        {
                                            cell.Value = o;
                                        }
                                        else
                                        cell.setRawValue(o);
                                    }

                                    break;
                                case "n": //CellType.Number:
                                        cell.setRawValue(double.Parse(Value, CultureInfo.InvariantCulture));
                                    break;
                                case "b": //CellType.Boolean:
                                    cell.Value = GetBooleanValue(this.Value);
                                    break;
                                case "e": //CellType.Error:
                                    cell.Value = Error.Parse(Value);
                                    //\\Value = new Error(s);
                                    break;
                                case "str": //CellType.FormulaString:
                                    cell.Value = XmlStringHelper.Decode(Value);
                                    break; //\\
                                default:
                                    throw new NotSupportedException();


                            }
                        }
                if (this.Formula != null)
                        this.SetFormula(cell, workbookResolver);
                if (this.InlineString != null)
                        cell.Value = this.InlineString;
                
                row.Add(cell);

                return cell.ColumnSpan + cell.Column.Index - 1;
            }

            private void SetFormula(Cell c, WorkbookResolver resolver)
            {
                Contract.Requires(Formula != null);
                var cellFormula = this.Formula;
                switch (cellFormula.Type)
                {
                    case CellFormulaType.Normal:
                        if (cellFormula.ca && string.IsNullOrEmpty(cellFormula.Text))
                            break;
                        resolver.AddFormula(c, cellFormula.Text);
                            //\\ this.Workbook.TempReader.AddFormula(this, f.Text);
                        break;
                    case CellFormulaType.Shared:
                        if (!string.IsNullOrEmpty(cellFormula.Text))
                        {
                            resolver.AddSharedFormula(c, cellFormula.Reference, cellFormula.Text, cellFormula.si);
                        }
                        else
                            resolver.AddSharedFormulaRef(c, cellFormula.si);
                        break;
                    case CellFormulaType.Array:
                        if (!string.IsNullOrEmpty(cellFormula.Text))
                        {
                            resolver.AddArrayFormula(c, cellFormula.Reference, cellFormula.Text, cellFormula.si);
                        }
                        else
                        {
                            throw new NotImplementedException(); //\\
                        }
                        break;
                    case CellFormulaType.DataTable:
                        throw new NotImplementedException();

                    default:
                        throw new NotSupportedException();
                }
            }
        }
    }
}
